/*******************************************************************************
 * thrill/net/tcp/socket_address.cpp
 *
 * Implements lookups and conversions to low-level socket address structs.
 *
 * Part of Project Thrill - http://project-thrill.org
 *
 * Copyright (C) 2015 Timo Bingmann <tb@panthema.net>
 *
 * All rights reserved. Published under the BSD-2 license in the LICENSE file.
 ******************************************************************************/

#include <fermat/common/logging.h>
#include <fermat/common/string.h>
#include <fermat/net/socket_address.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <algorithm>
#include <cerrno>
#include <string>
#include <vector>

namespace fermat {
namespace net {
/******************************************************************************/

SocketAddress::SocketAddress()
    : _resolve_error(0) 
{
    memset(&_sockaddr, 0, sizeof(_sockaddr));
}

bool SocketAddress::set(struct sockaddr* sa, socklen_t salen)
{
    memcpy(&_sockaddr, sa, std::min<socklen_t>(salen, sizeof(_sockaddr)));
    return true;
}

bool SocketAddress::set(const std::string& hostport) 
{
    std::string host = hostport;
    size_t colonpos = host.rfind(':');
    if (colonpos == std::string::npos) {
        return resolve(hostport.c_str());
    } else {
        std::string port = host.substr(colonpos + 1);
        host.erase(colonpos);
        return resolve(host.c_str(), port.c_str());
    }
}

bool SocketAddress::set(const char* hostname, const char* servicename) 
{
    return resolve(hostname, servicename);
}

std::string SocketAddress::toStringHost() const 
{
    char str[64];
    if (sockaddr()->sa_family == AF_INET) {
        if (inet_ntop(AF_INET,
                      &sockaddr_in()->sin_addr, str, sizeof(str)) == NULL) {
            LOG_ERROR("Error in inet_ntop: {}", strerror(errno));
            return "";
        }
        return str;
    } else if (sockaddr()->sa_family == AF_INET6) {
        if (inet_ntop(AF_INET6,
                      &sockaddr_in6()->sin6_addr, str, sizeof(str)) == NULL) {
            LOG_ERROR("Error in inet_ntop: {}", strerror(errno));
            return "";
        }
        return str;
    } else {
        return "";
    }
}

std::string SocketAddress::toStringHostPort() const 
{
    std::string host = toStringHost();
    if(host.empty()) {
        return host;
    } 
    return  host + ":" + to_str(getPort());
}

std::ostream& operator << (std::ostream& os, const SocketAddress& sa) 
{
    return os << sa.toStringHostPort();
}

bool SocketAddress::resolve(const char* hostname, const char* servicename) 
{
    struct addrinfo* result;
    struct addrinfo hints;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;

    int gai = getaddrinfo(hostname, servicename, &hints, &result);
    if (gai != 0) {
        memset(&_sockaddr, 0, sizeof(_sockaddr));
        _resolve_error = gai;
        return false;
    } else {
        set(result->ai_addr, result->ai_addrlen);
        freeaddrinfo(result);
        return isValid();
    }
}

const char* SocketAddress::getResolveError() const 
{
    return gai_strerror(_resolve_error);
}


SocketAddress
SocketAddress::resolveOne(const char* hostname, const char* servicename) 
{
    struct addrinfo* result;
    struct addrinfo hints;
    SocketAddress ret;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_INET;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;

    int s = getaddrinfo(hostname, servicename, &hints, &result);
    if (s != 0) {
        return ret;
    }

    ret.set(result->ai_addr, result->ai_addrlen);

    freeaddrinfo(result);

    return ret;
}

SocketAddress
SocketAddress::resolveWithPort(const char* hostname,
                               const char* defaultservice) 
{
    std::string host = hostname;

    std::string::size_type colonpos = host.rfind(':');
    if (colonpos == std::string::npos) {
        return resolveOne(hostname, defaultservice);
    }

    std::string servicename(host, colonpos + 1);
    host.erase(colonpos);

    return resolveOne(host.c_str(), servicename.c_str());
}

std::vector<SocketAddress>
SocketAddress::resolveAll(const char* hostname, const char* servicename) 
{
    std::vector<SocketAddress> salist;

    struct addrinfo* result;
    struct addrinfo hints;

    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;    /* Allow IPv4 or IPv6 */
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = 0;
    hints.ai_protocol = 0;

    int s = getaddrinfo(hostname, servicename, &hints, &result);
    if (s != 0) {
        return salist;
    }

    for (struct addrinfo* ap = result; ap != NULL; ap = ap->ai_next) {
        SocketAddress tmp;
        tmp.set(ap->ai_addr, ap->ai_addrlen);
        salist.push_back(tmp);
    }

    freeaddrinfo(result);

    return salist;
}
} // namespace net
} // namespace fermat